#pragma warning disable 3001
#pragma warning disable 3002

using System;
using System.ComponentModel.Design;
using System.Collections.Generic;
using System.Xml;
using System.Diagnostics;

using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.OLE.Interop;

using EnvDTE;
// using VSLangProj80;
// using VSLangProj;
using System.Windows.Forms;
using Microsoft.VisualStudio.Shell.Design;
using Microsoft.VisualStudio;
using System.Runtime.InteropServices;

namespace VsxFactory.Modeling.VisualStudio.Synchronization
{
    /// <summary>
    /// Helper for Visual Studio (from Daniel Cazzulino Blog)
    /// </summary>
    internal static class VsHelper
    {
        /// <summary>
        /// Get the current Hierarchy
        /// </summary>
        /// <param name="provider"></param>
        /// <returns></returns>
        public static IVsHierarchy GetCurrentHierarchy(System.IServiceProvider provider)
        {
            DTE vs = (DTE)provider.GetService(typeof(DTE));
            if (vs == null) throw new InvalidOperationException("DTE not found.");

            return ToHierarchy(vs.SelectedItems.Item(1).ProjectItem.ContainingProject);
        }


        /// <summary>
        /// Get the hierarchy corresponding to a Project
        /// </summary>
        /// <param name="project"></param>
        /// <returns></returns>
        public static IVsHierarchy ToHierarchy(EnvDTE.Project project)
        {
            if (project == null) throw new ArgumentNullException("project");

            string projectGuid = null;

            // DTE does not expose the project GUID that exists at in the msbuild project file.
            // Cannot use MSBuild object model because it uses a static instance of the Engine, 
            // and using the Project will cause it to be unloaded from the engine when the 
            // GC collects the variable that we declare.
            using (XmlReader projectReader = XmlReader.Create(project.FileName))
            {
                projectReader.MoveToContent();
                object nodeName = projectReader.NameTable.Add("ProjectGuid");
                while (projectReader.Read())
                {
                    if (Object.Equals(projectReader.LocalName, nodeName))
                    {
                        //   projectGuid = projectReader.ReadContentAsString();
                        projectGuid = projectReader.ReadElementContentAsString();
                        break;
                    }
                }
            }

            Debug.Assert(!String.IsNullOrEmpty(projectGuid));

            System.IServiceProvider serviceProvider = new ServiceProvider(project.DTE as
                Microsoft.VisualStudio.OLE.Interop.IServiceProvider);

            return VsShellUtilities.GetHierarchy(serviceProvider, new Guid(projectGuid));
        }


        /// <summary>
        /// Get a IVsProject3 from a project
        /// </summary>
        /// <param name="project"></param>
        /// <returns></returns>
        public static IVsProject3 ToVsProject(EnvDTE.Project project)
        {
            if (project == null) throw new ArgumentNullException("project");

            IVsProject3 vsProject = ToHierarchy(project) as IVsProject3;

            if (vsProject == null)
            {
                throw new ArgumentException("Project is not a VS project.");
            }

            return vsProject;
        }

        /// <summary>
        /// Get a Project from a hierarchy
        /// </summary>
        /// <param name="hierarchy"></param>
        /// <returns></returns>
        public static EnvDTE.Project ToDteProject(IVsHierarchy hierarchy)
        {
            if (hierarchy == null) throw new ArgumentNullException("hierarchy");

            object prjObject = null;
            if (hierarchy.GetProperty(0xfffffffe, -2027, out prjObject) >= 0)
            {
                return (EnvDTE.Project)prjObject;
            }
            else
            {
                throw new ArgumentException("Hierarchy is not a project.");
            }
        }

        /// <summary>
        /// Get a EnvDTE.Project from a IVsProject
        /// </summary>
        /// <param name="project"></param>
        /// <returns></returns>
        public static EnvDTE.Project ToDteProject(Microsoft.VisualStudio.Shell.Interop.IVsProject project)
        {
            if (project == null)
                throw new ArgumentNullException("project");
            return ToDteProject(project as IVsHierarchy);
        }

        /// <summary>
        /// Retrieving available types in current project and its references (without locking) 
        /// </summary>
        /// <param name="dteProject">Project the types, and referenced types of, we are interested in</param>
        /// <param name="baseType">All the types we are interested in will derive from the <paramref name="baseType"/></param>
        /// <param name="excludeGlobalTypes"></param>
        /// <param name="includePrivate">Include or not the private types</param>
        /// <returns>A dictionnary of Types, by their full name</returns>
        public static Dictionary<string, Type> GetAvailableTypes(Project dteProject, Type baseType, bool excludeGlobalTypes, bool includePrivate)
        {
            System.IServiceProvider serviceProvider = new ServiceProvider(dteProject.DTE as Microsoft.VisualStudio.OLE.Interop.IServiceProvider);

            DynamicTypeService typeService = serviceProvider.GetService(typeof(DynamicTypeService)) as DynamicTypeService;
            Debug.Assert(typeService != null, "No dynamic type service registered.");

            IVsHierarchy hier = VsHelper.ToHierarchy(dteProject);
            Dictionary<string, Type> availableTypes = new Dictionary<string, Type>();
            if (hier != null)
            {
                ITypeDiscoveryService discovery = typeService.GetTypeDiscoveryService(hier);

                if (discovery != null)
                    foreach (Type type in discovery.GetTypes(baseType, excludeGlobalTypes))
                    {
                        if (includePrivate || type.IsPublic)
                            if (!availableTypes.ContainsKey(type.FullName))
                            {
                                availableTypes.Add(type.FullName, type);
                            }
                    }
            }
            return availableTypes;
        }


        /// <summary>
        /// Retrieving available types in current project and its references (without locking) 
        /// </summary>
        /// <param name="provider"></param>
        /// <param name="includeReferences"></param>
        /// <returns></returns>
        public static Dictionary<string, Type> GetAvailableTypes(System.IServiceProvider provider, bool includeReferences)
        {
            Dictionary<string, Type> availableTypes = new Dictionary<string, Type>();

            IVsHierarchy hier = VsHelper.GetCurrentHierarchy(provider);
            Debug.Assert(hier != null, "No active hierarchy is selected.");

            DynamicTypeService typeService = (DynamicTypeService)provider.GetService(typeof(DynamicTypeService));
            Debug.Assert(typeService != null, "No dynamic type service registered.");

            ITypeDiscoveryService discovery = typeService.GetTypeDiscoveryService(hier);

            foreach (Type type in discovery.GetTypes(typeof(object), includeReferences))
            {
                // We will never allow non-public types selection, as it's terrible practice.
                if (type.IsPublic)
                {
                    if (!availableTypes.ContainsKey(type.FullName))
                    {
                        availableTypes.Add(type.FullName, type);
                    }
                }
            }

            return availableTypes;
        }
    }
}

